package wisecashier

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"sort"
	"strconv"
	"strings"
	"time"
	"crypto"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
    "encoding/pem"
	"crypto/sha256"
	"encoding/base64"
	"github.com/pkg/errors"
	"github.com/astaxie/beego"
)

type Auth struct {
	merchanNo   string
	//appsecret string
	requrl      string
	keyPEMBlock []byte
}

type HeadParams struct {
	merchan_no  string    //
	signature   string    //
	version     string    // 
	timestamp   string    // 
}

func NewSDK(restURL, merchanNo string, keyPEMBlock []byte) *Auth {
	return &Auth{requrl: restURL, merchanNo: merchanNo, keyPEMBlock: keyPEMBlock}
}

func (this *Auth) request(params map[string]string, methodType string,response interface{}) (respMap map[string]interface{}, err error) {
	sign, e := this.reqsign(params)
	if e != nil {
		err = e
		return
	}
	args := HeadParams{
		merchan_no:  this.merchanNo,
		signature:   sign,
		version:     "1.0",
		timestamp:   strconv.FormatInt(time.Now().Unix(), 10),
	}
	urlParams := url.Values{}
	for k, v := range params {
		urlParams.Add(k, v)
	}
	var req *http.Request
	client := &http.Client{}
	body := strings.NewReader(urlParams.Encode())
	req, err = http.NewRequest(methodType, this.requrl, body)
	if err != nil {
		return
	}
	req.Header.Add("MerchantNO", args.merchan_no)
	req.Header.Add("Signature", args.signature)
	req.Header.Add("Version", args.version)
	req.Header.Add("Timestamp", args.timestamp)
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded") //必须
	resp, err := client.Do(req)
	if err != nil {
		return
	}
	//resCode = resp.StatusCode
	defer resp.Body.Close()
	bodyBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return
	}
	beego.Debug("wisecashier request:", string(bodyBytes))
	err = json.Unmarshal(bodyBytes, &response)
	if err != nil {
		err = errors.New(fmt.Sprintf("%s %s", err.Error(), string(bodyBytes)))
	}
	return
}

// SHA256 sha256
func sHA256(data string) []byte {
	h := sha256.New()
	h.Write([]byte(data))
	return h.Sum(nil)
}

// Base64Encode base64 encode
func base64Encode(data []byte) string {
	return base64.StdEncoding.EncodeToString(data)
}

func rsaSign(origdata string, privateKey []byte) (string, error) {
	digest := sHA256(origdata)
	block, _ := pem.Decode(privateKey)
	if block == nil {
		return "", errors.New("private key error")
	}
	privInterface, err := x509.ParsePKCS1PrivateKey(block.Bytes)
	if err != nil {
		return "", err
	}
	body, err := rsa.SignPKCS1v15(rand.Reader, privInterface, crypto.SHA256, digest)
	if err != nil {
		return "", err
	}
	return base64Encode(body), nil
}

func (this *Auth) reqsign(fields map[string]string) (string, error) {
    basestring := ""
	var keys []string
	for k := range fields {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	for _, k := range keys {
		basestring += url.QueryEscape(k) + "=" + url.QueryEscape(fields[k]) + "&"
	}
	midstring := strings.TrimSuffix(basestring, "&") + "," + strconv.FormatInt(time.Now().Unix(), 10)
	sha, err := rsaSign(midstring, this.keyPEMBlock) 
	if err != nil {
        return "", err
    }
	return sha, nil
}

func (this *Auth) Execute(params map[string]interface{},response interface{}) (respMap map[string]interface{}, err error) {
	var paramsMap map[string]string
	paramsMap = make(map[string]string)

	for k, v := range params {
		if s, ok := v.(string); ok {
			paramsMap[k] = s
		} else if _, ok := v.(int); ok {
			paramsMap[k] = strconv.Itoa(v.(int))
		} else {
			err = errors.New("格式错误，map 格式只支持string, int")
			return
		}
	}

	respMap, err = this.request(paramsMap, "POST", response)
	return
}
